x11: Improve fallbacks for text
authorBenjamin Otte <otte@redhat.com>
Thu, 23 Nov 2017 00:59:19 +0000 (01:59 +0100)
committerBenjamin Otte <otte@redhat.com>
Sun, 3 Dec 2017 04:46:46 +0000 (05:46 +0100)
(1) Try all passed in formats in order if one of them fails.
(2) Don't blindly accept all formats, make sure they are mime types
(3) Add a bunch of special non-mime types that plug converters to
    get to mime types

gdk/x11/gdkclipboard-x11.c
gdk/x11/gdkselectioninputstream-x11.c
gdk/x11/gdkselectioninputstream-x11.h
gdk/x11/gdktextlistconverter-x11.c [new file with mode: 0644]
gdk/x11/gdktextlistconverter-x11.h [new file with mode: 0644]
gdk/x11/meson.build

index a88c86d1ce33baf76f7f909a20c7c5e1031aab7d..aef23b7ee39fb999223771be3c0f925958ccca37 100644 (file)
 
 #include "gdkclipboardprivate.h"
 #include "gdkclipboard-x11.h"
-#include "gdkselectioninputstream-x11.h"
+
+#include "gdkintl.h"
 #include "gdkprivate-x11.h"
+#include "gdkselectioninputstream-x11.h"
+#include "gdktextlistconverter-x11.h"
 
 #include <string.h>
 #include <X11/Xatom.h>
@@ -57,6 +60,121 @@ G_DEFINE_TYPE (GdkX11Clipboard, gdk_x11_clipboard, GDK_TYPE_CLIPBOARD)
        ? XMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100         \
        : XExtendedMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100)
 
+static GInputStream * 
+text_list_convert (GdkX11Clipboard *cb,
+                   GInputStream    *stream,
+                   const char      *encoding,
+                   int              format)
+{
+  GInputStream *converter_stream;
+  GConverter *converter;
+
+  converter = gdk_x11_text_list_converter_to_utf8_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
+                                                       encoding,
+                                                       format);
+  converter_stream = g_converter_input_stream_new (stream, converter);
+
+  g_object_unref (converter);
+  g_object_unref (stream);
+
+  return converter_stream;
+}
+
+static GInputStream * 
+no_convert (GdkX11Clipboard *cb,
+            GInputStream    *stream,
+            const char      *encoding,
+            int              format)
+{
+  return stream;
+}
+
+static const struct {
+  const char *x_target;
+  const char *mime_type;
+  GInputStream * (* convert) (GdkX11Clipboard *, GInputStream *, const char *, int);
+} special_targets[] = {
+  { "UTF8_STRING", "text/plain;charset=utf-8",   no_convert },
+  { "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert },
+  { "TEXT", "text/plain;charset=utf-8",          text_list_convert },
+  { "STRING", "text/plain;charset=utf-8",        text_list_convert }
+};
+
+static void
+print_atoms (GdkX11Clipboard *cb,
+             const char      *prefix,
+             const Atom      *atoms,
+             gsize            n_atoms)
+{
+  GDK_NOTE(CLIPBOARD,
+           GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
+           gsize i;
+            
+           g_printerr ("%s: %s [ ", cb->selection, prefix);
+           for (i = 0; i < n_atoms; i++)
+             {
+               g_printerr ("%s%s", i > 0 ? ", " : "", gdk_x11_get_xatom_name_for_display (display , atoms[i]));
+             }
+           g_printerr (" ]\n");
+          ); 
+}
+
+static GSList *
+gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
+{
+  GSList *targets;
+  const char * const *mime_types;
+  gsize i, j, n_mime_types;
+
+  targets = NULL;
+  mime_types = gdk_content_formats_get_mime_types (formats, &n_mime_types);
+
+  for (i = 0; i < n_mime_types; i++)
+    {
+      for (j = 0; j < G_N_ELEMENTS (special_targets); j++)
+        {
+          if (g_str_equal (mime_types[i], special_targets[j].mime_type))
+            targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target));
+        }
+      targets = g_slist_prepend (targets, (gpointer) mime_types[i]);
+    }
+
+  return g_slist_reverse (targets);
+}
+
+static GdkContentFormats *
+gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display,
+                                      const Atom *atoms,
+                                      gsize       n_atoms)
+{
+  GdkContentFormatsBuilder *builder;
+  gsize i, j;
+
+  builder = gdk_content_formats_builder_new ();
+  for (i = 0; i < n_atoms; i++)
+    {
+      const char *name;
+
+      name = gdk_x11_get_xatom_name_for_display (display , atoms[i]);
+      if (strchr (name, '/'))
+        {
+          gdk_content_formats_builder_add_mime_type (builder, name);
+          continue;
+        }
+
+      for (j = 0; j < G_N_ELEMENTS (special_targets); j++)
+        {
+          if (g_str_equal (name, special_targets[j].x_target))
+            {
+              gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type);
+              break;
+            }
+        }
+    }
+
+  return gdk_content_formats_builder_free (builder);
+}
+
 static void
 gdk_x11_clipboard_request_targets_finish (GObject      *source_object,
                                           GAsyncResult *res,
@@ -66,11 +184,8 @@ gdk_x11_clipboard_request_targets_finish (GObject      *source_object,
   GdkX11Clipboard *cb = user_data;
   GdkDisplay *display;
   GdkContentFormats *formats;
-  GdkContentFormatsBuilder *builder;
   GBytes *bytes;
   GError *error = NULL;
-  const Atom *atoms;
-  guint i, n_atoms;
 
   bytes = g_input_stream_read_bytes_finish (stream, res, &error);
   if (bytes == NULL)
@@ -88,17 +203,15 @@ gdk_x11_clipboard_request_targets_finish (GObject      *source_object,
       return;
     }
 
-  display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
+  print_atoms (cb,
+               "received targets",
+               g_bytes_get_data (bytes, NULL),
+               g_bytes_get_size (bytes) / sizeof (Atom));
 
-  atoms = g_bytes_get_data (bytes, NULL);
-  n_atoms = g_bytes_get_size (bytes) / sizeof (Atom);
-  builder = gdk_content_formats_builder_new ();
-  for (i = 0; i < n_atoms; i++)
-    {
-      gdk_content_formats_builder_add_mime_type (builder, gdk_x11_get_xatom_name_for_display (display , atoms[i]));
-    }
-  gdk_content_formats_builder_add_formats (builder, gdk_clipboard_get_formats (GDK_CLIPBOARD (cb)));
-  formats = gdk_content_formats_builder_free (builder);
+  display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
+  formats = gdk_x11_clipboard_formats_from_atoms (display,
+                                                  g_bytes_get_data (bytes, NULL),
+                                                  g_bytes_get_size (bytes) / sizeof (Atom));
   GDK_NOTE(CLIPBOARD, char *s = gdk_content_formats_to_string (formats); g_printerr ("%s: got formats: %s\n", cb->selection, s); g_free (s));
 
   /* union with previously loaded formats */
@@ -122,13 +235,22 @@ gdk_x11_clipboard_request_targets_got_stream (GObject      *source,
   GInputStream *stream;
   GdkDisplay *display;
   GError *error = NULL;
+  const char *type;
+  int format;
 
-  stream = gdk_x11_selection_input_stream_new_finish (result, &error);
+  stream = gdk_x11_selection_input_stream_new_finish (result, &type, &format, &error);
   if (stream == NULL)
     {
       g_object_unref (cb);
       return;
     }
+  else if (!g_str_equal (type, "ATOM") || format != 32)
+    {
+      g_input_stream_close (stream, NULL, NULL);
+      g_object_unref (stream);
+      g_object_unref (cb);
+      return;
+    }
 
   display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
 
@@ -213,13 +335,61 @@ gdk_x11_clipboard_read_got_stream (GObject      *source,
   GTask *task = data;
   GError *error = NULL;
   GInputStream *stream;
+  const char *type;
+  int format;
   
-  stream = gdk_x11_selection_input_stream_new_finish (res, &error);
-  /* XXX: We could try more types here */
+  stream = gdk_x11_selection_input_stream_new_finish (res, &type, &format, &error);
   if (stream == NULL)
-    g_task_return_error (task, error);
+    {
+      GSList *targets, *next;
+      
+      targets = g_task_get_task_data (task);
+      next = targets->next;
+      if (next)
+        {
+          GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task));
+
+          GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading %s failed, trying %s next\n",
+                                          cb->selection, (char *) targets->data, (char *) next->data));
+          targets->next = NULL;
+          g_task_set_task_data (task, next, (GDestroyNotify) g_slist_free);
+          gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
+                                                    cb->selection,
+                                                    next->data,
+                                                    cb->timestamp,
+                                                    g_task_get_priority (task),
+                                                    g_task_get_cancellable (task),
+                                                    gdk_x11_clipboard_read_got_stream,
+                                                    task);
+          g_error_free (error);
+          return;
+        }
+
+      g_task_return_error (task, error);
+    }
   else
-    g_task_return_pointer (task, stream, g_object_unref);
+    {
+      GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task));
+      const char *mime_type = ((GSList *) g_task_get_task_data (task))->data;
+      gsize i;
+
+      for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
+        {
+          if (g_str_equal (mime_type, special_targets[i].x_target))
+            {
+              GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading with converter from %s to %s\n",
+                                              cb->selection, mime_type, special_targets[i].mime_type));
+              mime_type = g_intern_string (special_targets[i].mime_type);
+              g_task_set_task_data (task, g_slist_prepend (NULL, (gpointer) mime_type), (GDestroyNotify) g_slist_free);
+              stream = special_targets[i].convert (cb, stream, type, format);
+              break;
+            }
+        }
+
+      GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading clipboard as %s now\n",
+                                      cb->selection, mime_type));
+      g_task_return_pointer (task, stream, g_object_unref);
+    }
 
   g_object_unref (task);
 }
@@ -233,20 +403,27 @@ gdk_x11_clipboard_read_async (GdkClipboard        *clipboard,
                               gpointer             user_data)
 {
   GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
-  const char * const *mime_types;
+  GSList *targets;
   GTask *task;
 
   task = g_task_new (clipboard, cancellable, callback, user_data);
   g_task_set_priority (task, io_priority);
   g_task_set_source_tag (task, gdk_x11_clipboard_read_async);
-  g_task_set_task_data (task, gdk_content_formats_ref (formats), (GDestroyNotify) gdk_content_formats_unref);
 
-  /* XXX: Sort differently? */
-  mime_types = gdk_content_formats_get_mime_types (formats, NULL);
+  targets = gdk_x11_clipboard_formats_to_targets (formats);
+  g_task_set_task_data (task, targets, (GDestroyNotify) g_slist_free);
+  if (targets == NULL)
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                               _("No compatible transfer format found"));
+      return;
+    }
 
+  GDK_NOTE(CLIPBOARD, g_printerr ("%s: new read for %s (%u other options)\n",
+                                  cb->selection, (char *) targets->data, g_slist_length (targets->next)));
   gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
                                             cb->selection,
-                                            mime_types[0],
+                                            targets->data,
                                             cb->timestamp,
                                             io_priority,
                                             cancellable,
@@ -273,12 +450,10 @@ gdk_x11_clipboard_read_finish (GdkClipboard  *clipboard,
     {
       if (out_mime_type)
         {
-          GdkContentFormats *formats;
-          const char * const  *mime_types;
+          GSList *targets;
 
-          formats = g_task_get_task_data (task);
-          mime_types = gdk_content_formats_get_mime_types (formats, NULL);
-          *out_mime_type = mime_types[0];
+          targets = g_task_get_task_data (task);
+          *out_mime_type = targets->data;
         }
       g_object_ref (stream);
     }
index a694d05cb720a638f0dd06e91788f499d485cc61..9ba704d6d02fde72b9b0a7a2bd96a2bfed280e96 100644 (file)
@@ -40,6 +40,9 @@ struct GdkX11SelectionInputStreamPrivate {
   Atom xtarget;
   char *property;
   Atom xproperty;
+  const char *type;
+  Atom xtype;
+  int format;
 
   GTask *pending_task;
   guchar *pending_data;
@@ -150,6 +153,8 @@ gdk_x11_selection_input_stream_complete (GdkX11SelectionInputStream *stream)
   if (priv->complete)
     return;
 
+  GDK_NOTE(SELECTION, g_printerr ("%s:%s: transfer complete\n",
+                                  priv->selection, priv->target));
   priv->complete = TRUE;
 
   g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0));
@@ -457,7 +462,8 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev,
           }
         else
           {
-            bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &type, &format);
+            bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &priv->xtype, &priv->format);
+            priv->type = gdk_x11_get_xatom_name_for_display (priv->display, priv->xtype);
 
             g_task_return_pointer (task, g_object_ref (stream), g_object_unref);
 
@@ -477,6 +483,9 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev,
                   }
                 else
                   {
+                    GDK_NOTE(SELECTION, g_printerr ("%s:%s: reading %zu bytes\n",
+                                                    priv->selection, priv->target,
+                                                    g_bytes_get_size (bytes)));
                     g_async_queue_push (priv->chunks, bytes);
 
                     gdk_x11_selection_input_stream_complete (stream);
@@ -536,9 +545,11 @@ gdk_x11_selection_input_stream_new_async (GdkDisplay          *display,
 
 GInputStream *
 gdk_x11_selection_input_stream_new_finish (GAsyncResult  *result,
+                                           const char   **type,
+                                           int           *format,
                                            GError       **error)
 {
-  GInputStream *stream;
+  GdkX11SelectionInputStream *stream;
   GTask *task;
 
   g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
@@ -547,7 +558,16 @@ gdk_x11_selection_input_stream_new_finish (GAsyncResult  *result,
 
   stream = g_task_propagate_pointer (task, error);
   if (stream)
-    g_object_ref (stream);
-  return stream;
+    {
+      GdkX11SelectionInputStreamPrivate *priv = gdk_x11_selection_input_stream_get_instance_private (stream);
+
+      if (type)
+        *type = priv->type;
+      if (format)
+        *format = priv->format;
+      g_object_ref (stream);
+    }
+
+  return G_INPUT_STREAM (stream);
 }
 
index c8bd19d55e8c5211ca39f08728a9ee047a3a889e..18c076302541bf73bf4bff402babd38de58265f0 100644 (file)
@@ -59,6 +59,8 @@ void           gdk_x11_selection_input_stream_new_async     (GdkDisplay
                                                              GAsyncReadyCallback         callback,
                                                              gpointer                    user_data);
 GInputStream * gdk_x11_selection_input_stream_new_finish    (GAsyncResult               *result,
+                                                             const char                **type,
+                                                             int                        *format,
                                                              GError                    **error);
 
 
diff --git a/gdk/x11/gdktextlistconverter-x11.c b/gdk/x11/gdktextlistconverter-x11.c
new file mode 100644 (file)
index 0000000..7fe56d1
--- /dev/null
@@ -0,0 +1,169 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gdktextlistconverter-x11.h"
+
+#include "gdkintl.h"
+#include "gdkprivate-x11.h"
+
+#define GDK_X11_TEXT_LIST_CONVERTER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDK_TYPE_X11_TEXT_LIST_CONVERTER, GdkX11TextListConverterClass))
+#define GDK_IS_X11_TEXT_LIST_CONVERTER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDK_TYPE_X11_TEXT_LIST_CONVERTER))
+#define GDK_X11_TEXT_LIST_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDK_TYPE_X11_TEXT_LIST_CONVERTER, GdkX11TextListConverterClass))
+
+typedef struct _GdkX11TextListConverterClass GdkX11TextListConverterClass;
+
+struct _GdkX11TextListConverter
+{
+  GObject parent_instance;
+
+  GdkDisplay *display;
+  
+  const char *encoding; /* interned */
+  gint format;
+};
+
+struct _GdkX11TextListConverterClass
+{
+  GObjectClass parent_class;
+};
+
+static GConverterResult
+gdk_x11_text_list_converter_convert (GConverter       *converter,
+                                     const void       *inbuf,
+                                     gsize             inbuf_size,
+                                     void             *outbuf,
+                                     gsize             outbuf_size,
+                                     GConverterFlags   flags,
+                                     gsize            *bytes_read,
+                                     gsize            *bytes_written,
+                                     GError          **error)
+{
+  GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (converter);
+  gint count;
+  char **list;
+
+  if (!(flags & G_CONVERTER_INPUT_AT_END))
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+                           _("Need complete input to do conversion"));
+      return G_CONVERTER_ERROR;
+    }
+
+  count = _gdk_x11_display_text_property_to_utf8_list (conv->display,
+                                                       conv->encoding,
+                                                       conv->format,
+                                                       inbuf,
+                                                       inbuf_size,
+                                                       &list);
+  if (count < 0)
+    {
+      /* XXX: add error handling from gdkselection-x11.c */
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
+                           _("Not enough space in destination"));
+      return G_CONVERTER_ERROR;
+    }
+  else if (count == 0)
+    {
+      if (outbuf_size < 1)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
+                               _("Not enough space in destination"));
+          return G_CONVERTER_ERROR;
+        }
+      ((gchar *) outbuf)[0] = 0;
+      *bytes_read = inbuf_size;
+      *bytes_written = 1;
+      return G_CONVERTER_FINISHED;
+    }
+  else
+    {
+      gsize len = strlen (list[0]) + 1;
+
+      if (outbuf_size < len)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
+                               _("Not enough space in destination"));
+          return G_CONVERTER_ERROR;
+        }
+      memcpy (outbuf, list[0], len);
+      g_strfreev (list);
+      *bytes_read = inbuf_size;
+      *bytes_written = len;
+      return G_CONVERTER_FINISHED;
+    }
+}
+
+static void
+gdk_x11_text_list_converter_reset (GConverter *converter)
+{
+}
+
+static void
+gdk_x11_text_list_converter_iface_init (GConverterIface *iface)
+{
+  iface->convert = gdk_x11_text_list_converter_convert;
+  iface->reset = gdk_x11_text_list_converter_reset;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GdkX11TextListConverter, gdk_x11_text_list_converter, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
+                                               gdk_x11_text_list_converter_iface_init))
+
+static void
+gdk_x11_text_list_converter_finalize (GObject *object)
+{
+  GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (object);
+
+  g_object_unref (conv->display);
+
+  G_OBJECT_CLASS (gdk_x11_text_list_converter_parent_class)->finalize (object);
+}
+
+static void
+gdk_x11_text_list_converter_class_init (GdkX11TextListConverterClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gdk_x11_text_list_converter_finalize;
+}
+
+static void
+gdk_x11_text_list_converter_init (GdkX11TextListConverter *local)
+{
+}
+
+GConverter *
+gdk_x11_text_list_converter_to_utf8_new (GdkDisplay *display,
+                                         const char *encoding,
+                                         int         format)
+{
+  GdkX11TextListConverter *conv;
+
+  conv = g_object_new (GDK_TYPE_X11_TEXT_LIST_CONVERTER, NULL);
+
+  conv->display = g_object_ref (display);
+  conv->encoding = g_intern_string (encoding);
+  conv->format = format;
+
+  return G_CONVERTER (conv);
+}
+
diff --git a/gdk/x11/gdktextlistconverter-x11.h b/gdk/x11/gdktextlistconverter-x11.h
new file mode 100644 (file)
index 0000000..9017a8b
--- /dev/null
@@ -0,0 +1,44 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GDK_X11_TEXT_LIST_CONVERTER_H__
+#define __GDK_X11_TEXT_LIST_CONVERTER_H__
+
+#include <gio/gio.h>
+#include <gdk/gdktypes.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_X11_TEXT_LIST_CONVERTER         (gdk_x11_text_list_converter_get_type ())
+#define GDK_X11_TEXT_LIST_CONVERTER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_X11_TEXT_LIST_CONVERTER, GdkX11TextListConverter))
+#define GDK_IS_X11_TEXT_LIST_CONVERTER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_X11_TEXT_LIST_CONVERTER))
+
+typedef struct _GdkX11TextListConverter   GdkX11TextListConverter;
+
+GType              gdk_x11_text_list_converter_get_type         (void) G_GNUC_CONST;
+
+GConverter *       gdk_x11_text_list_converter_to_utf8_new      (GdkDisplay     *display,
+                                                                 const char     *encoding,
+                                                                 int             format);
+
+
+G_END_DECLS
+
+#endif /* __GDK_X11_TEXT_LIST_CONVERTER_H__ */
index 57ed429df40927fc5fcca80eff3a0203a42da24b..db74295d8a6c29b5fa0217c7fa0d8e97b04af7d9 100644 (file)
@@ -22,6 +22,7 @@ gdk_x11_sources = files([
   'gdkscreen-x11.c',
   'gdkselection-x11.c',
   'gdkselectioninputstream-x11.c',
+  'gdktextlistconverter-x11.c',
   'gdkvisual-x11.c',
   'gdkvulkancontext-x11.c',
   'gdkwindow-x11.c',